package ch.loway.oss.ari4java.codegen; import ch.loway.oss.ari4java.codegen.genJava.JavaGen; import ch.loway.oss.ari4java.codegen.genJava.JavaInterface; import ch.loway.oss.ari4java.codegen.genJava.JavaPkgInfo; import ch.loway.oss.ari4java.codegen.models.Action; import ch.loway.oss.ari4java.codegen.models.Apis; import ch.loway.oss.ari4java.codegen.models.AriBuilderInterface; import ch.loway.oss.ari4java.codegen.models.ClassTranslator; import ch.loway.oss.ari4java.codegen.models.Operation; import ch.loway.oss.ari4java.codegen.models.Model; import ch.loway.oss.ari4java.codegen.models.ModelField; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * The model mapper keeps a list of interfaces and actual implementations. * * $Id$ * @author lenz */ public class DefMapper { List<Model> mymodels = new ArrayList<Model>(); List<String> knownModels = new ArrayList<String>(); List<Apis> myAPIs = new ArrayList<Apis>(); Map<String, JavaInterface> interfaces = new HashMap<String, JavaInterface>(); String myAbsoluteProjectFolder = "."; /** * Loads definitions from a module. * * @param f The source .json file * @param apiVersion The version of the API we are working with * @param modelHasEvents whether this file generates WS events * @throws IOException */ public void parseJsonDefinition( File f, String apiVersion, boolean modelHasEvents ) throws IOException { ObjectMapper om = new ObjectMapper(); System.out.println( "Loading as: " + f.getAbsolutePath() ); JsonNode rootNode = om.readTree(f); List<Model> lModels = loadModels(rootNode.get("models"), f, apiVersion ); Apis api1 = loadApis( rootNode.get("apis"), f, apiVersion ); mymodels.addAll(lModels); myAPIs.add(api1); if ( modelHasEvents ) { Model typeMessage = null; List<Model> otherModels = new ArrayList<Model>(); for ( Model m: lModels ) { if ( m.className.equalsIgnoreCase("Message") ) { typeMessage = m; } else { otherModels.add(m); } } String defs = ""; for ( Model m: otherModels ) { if ( defs.length() > 0 ) { defs += ", "; } defs += "@Type(value = " + m.getImplName() + ".class, name = \"" + m.getInterfaceName() + "\")\n"; } typeMessage.additionalPreambleText = "" + " @JsonTypeInfo( " + " use = JsonTypeInfo.Id.NAME, " + " include = JsonTypeInfo.As.PROPERTY, " + " property = \"type\") \n " + " @JsonSubTypes({ " + defs + " }) \n\n"; typeMessage.imports.add("com.fasterxml.jackson.annotation.JsonSubTypes"); typeMessage.imports.add("com.fasterxml.jackson.annotation.JsonSubTypes.Type"); typeMessage.imports.add("com.fasterxml.jackson.annotation.JsonTypeInfo"); } // Now generate the interface for ( Model m: mymodels ) { JavaInterface j = interfaces.get(m.getInterfaceName()); if ( j == null ) { j = new JavaInterface(); j.pkgName = "ch.loway.oss.ari4java.generated"; j.className = m.getInterfaceName(); interfaces.put(m.getInterfaceName(), j); } m.registerInterfaces(j, apiVersion); } //for ( Apis api: myAPIs ) { JavaInterface j = interfaces.get(api1.getInterfaceName()); if ( j == null ) { j = new JavaInterface(); j.pkgName = "ch.loway.oss.ari4java.generated"; j.className = api1.getInterfaceName(); interfaces.put(api1.getInterfaceName(), j); } api1.registerInterfaces(j, apiVersion); //} // // // for ( String ifName: interfaces.keySet() ) { // JavaInterface ji = interfaces.get(ifName); //// saveToDisk(ji); // } } /** * Generate all files. * * * @throws IOException */ public void generateAllClasses() throws IOException { AriBuilderInterface abi = generateInterfaces(); generateModels(); generateApis(); generateProperties( abi ); generateImplementationClasses( abi ); } /** * Generate all interfaces. * * @throws IOException */ public AriBuilderInterface generateInterfaces() throws IOException { // AriBuilderInterface abi = new AriBuilderInterface(); for ( String ifName: interfaces.keySet() ) { JavaInterface ji = interfaces.get(ifName); abi.knownInterfaces.add(ifName); saveToDisk(ji); } // generate the AriBuilder class saveToDisk( "classes/", "ch.loway.oss.ari4java.generated", "AriBuilder", abi.toString() ); return abi; } /** * Generates a model. * For each model. let's find out the minimal interface it should implement. * * @throws IOException */ public void generateModels() throws IOException { for (Model m : mymodels) { String minIf = m.getInterfaceName(); JavaInterface ji = interfaces.get(minIf); m.setMinimalInterface(ji); saveToDisk(m); } } /** * Save APIs to disk. * * @throws IOException */ public void generateApis() throws IOException { for ( Apis api: myAPIs ) { String minIf = api.getInterfaceName(); JavaInterface ji = interfaces.get(minIf); api.setMinimalInterface(ji); saveToDisk(api); } } /** * Write a properties file for each API version. * * @throws IOException */ public void generateProperties( AriBuilderInterface abi ) throws IOException { Map<String,Set<Model>> mM = new HashMap<String, Set<Model>>(); Map<String,Set<Apis>> mA = new HashMap<String, Set<Apis>>(); for ( Apis api: myAPIs ) { String ver = api.apiVersion; if ( !mA.containsKey(ver) ) { mA.put( ver, new HashSet<Apis>() ); } mA.get(ver).add(api); } for ( Model mod: mymodels ) { String ver = mod.apiVersion; if ( !mM.containsKey(ver) ) { mM.put( ver, new HashSet<Model>() ); } mM.get(ver).add(mod); } for ( String ver: mM.keySet() ) { //writeProperties(ver, mA.get(ver), mM.get(ver)); writeAriBuilder(ver, abi, mA.get(ver), mM.get(ver)); } } /** * Generates the translators from the abstract class to the concrete one. * * @param abi * @throws IOException */ public void generateImplementationClasses( AriBuilderInterface abi ) throws IOException { Map<String,ClassTranslator> mTranslators = new HashMap<String,ClassTranslator>(); for ( Apis api: myAPIs ) { String ver = api.apiVersion; ClassTranslator ct = getClassTranslator(mTranslators, ver); ct.setClass( api.className, api.className + "_impl_" + ver ); } for ( Model mod: mymodels ) { String ver = mod.apiVersion; ClassTranslator ct = getClassTranslator(mTranslators, ver); ct.setClass( mod.className, mod.className + "_impl_" + ver ); } for ( ClassTranslator ct: mTranslators.values() ) { saveToDisk(ct); } } private ClassTranslator getClassTranslator( Map<String,ClassTranslator> mTranslators, String apiVer ) { if ( !mTranslators.containsKey( apiVer ) ) { ClassTranslator ct = new ClassTranslator(); ct.apiVersion = apiVer; mTranslators.put( apiVer, ct ); } return mTranslators.get( apiVer); } /** * * @param f * @return */ private String genActionClassName(File f) { String fileName = f.getName().replace(".json", ""); return JavaGen.addPrefixAndCapitalize( "Action", fileName ); } private List<Model> loadModels(JsonNode models, File f, String apiVersion) throws IOException { List<Model> lModelsAdded = new ArrayList<Model>(); for (JsonNode modelName : models) { // Creazione di un modello Model currentModel = new Model(); String thisModel = txt(modelName.get("id")); String thisModelDesc = txt(modelName.get("description")); currentModel.setPackageInfo(thisModel, apiVersion); currentModel.description = thisModelDesc; currentModel.comesFromFile = f.getName(); currentModel.extendsModel = extendsObject(modelName); currentModel.subTypes = subTypes(modelName); knownModels.add(thisModel); JsonNode properties = modelName.get("properties"); Iterator<String> propNames = properties.fieldNames(); while (propNames.hasNext()) { String field = propNames.next(); JsonNode property = properties.get(field); String javaType = remapAbstractType(txt(property.get("type"))); String javaConcreteType = remapConcreteType(txt(property.get("type")), apiVersion); String comment = txt(property.get("description")); ModelField mf = new ModelField(); mf.field = field; mf.typeInterface = javaType; mf.typeConcrete = javaConcreteType; mf.comment = comment; currentModel.fields.add(mf); } lModelsAdded.add(currentModel); } for (Model m : lModelsAdded) { if (m.subTypes != null) { for (Model mm : lModelsAdded) { if (m.subTypes.contains(mm.className)) { mm.extendsModel = remapAbstractType(m.className); } } } } return lModelsAdded; } private Apis loadApis(JsonNode apis, File f, String apiVersion ) throws IOException { Apis api = new Apis(); api.setPackageInfo( genActionClassName(f), apiVersion); for (JsonNode apiEntry : apis) { Action action = new Action(); action.path = txt(apiEntry.get("path")); action.description = txt(apiEntry.get("description")); action.javaFile = f.getName(); for (JsonNode operation : apiEntry.get("operations")) { Operation op = new Operation(); action.operations.add(op); op.method = txt(operation.get("httpMethod")); if ("websocket".equalsIgnoreCase(txt(operation.get("upgrade")))) { op.wsUpgrade = true; } op.nickname = txt(operation.get("nickname")); op.responseInterface = remapAbstractType(txt(operation.get("responseClass"))); op.responseConcreteClass = remapConcreteType(txt(operation.get("responseClass")), apiVersion); op.description = txt(operation.get("summary") ) + "\n" + txt(operation.get("notes") ); JsonNode parameters = operation.get("parameters"); if (parameters != null) { for (JsonNode parameter : parameters) { Operation.Param p = new Operation.Param(); p.javaType = remapAbstractType(txt(parameter.get("dataType"))); p.name = txt(parameter.get("name")); p.required = txt(parameter.get("required")).equalsIgnoreCase("true"); p.type = Operation.ParamType.build( txt(parameter.get("paramType"))); op.parms.add(p); } } JsonNode errorResponses = operation.get("errorResponses"); if (errorResponses != null) { for (JsonNode errorResponse : errorResponses) { Operation.ErrorResp err = new Operation.ErrorResp(); err.code = errorResponse.get("code").asInt(); err.reason = errorResponse.get("reason").asText(); op.errorCodes.add(err); } } } //System.out.println( action.toString() ); api.actions.add(action); } myAPIs.add(api); return api; } public void saveToDisk( String baseJavaClasses, String pkgName, String className, String classText ) throws IOException { String fName = myAbsoluteProjectFolder + "/" + baseJavaClasses + pkgName.replace(".", "/" ) + "/" + className + ".java"; FileWriter outFile = new FileWriter( fName ); PrintWriter out = new PrintWriter(outFile); out.println( classText ); out.close(); } public void saveToDisk( Model model ) throws IOException { saveToDisk( "classes/", model.getModelPackage(), model.getImplName(), model.toString() ); } public void saveToDisk( Apis api ) throws IOException { saveToDisk( "classes/", api.getActionsPackage(), api.getImplName(), api.toString() ); } public void saveToDisk( JavaInterface ji ) throws IOException { saveToDisk( "classes/", "ch.loway.oss.ari4java.generated", ji.className, ji.toString() ); } public void saveToDisk( ClassTranslator ct ) throws IOException { saveToDisk( "classes/", ct.getBaseApiPackage(), ct.getImplName(), ct.toString() ); } public void clean(String apiVersion) throws IOException { String base = "classes/ch/loway/oss/ari4java/generated"; cleanPath(base+"/"+apiVersion+"/actions"); cleanPath(base+"/"+apiVersion+"/models"); } private void cleanPath(String path) throws IOException { System.out.println("clean: "+path); File p = new File(path); p.mkdirs(); for (File f : p.listFiles()) { if (f.isFile()) { f.delete(); } } } private String txt( JsonNode n ) { if ( n == null ) { return ""; } else { return n.asText(); } } public String extendsObject( JsonNode model ) { if ( model.get( "extends" ) != null ) { return remapAbstractType( model.get("extends").asText() ); } return ""; } public Set<String> subTypes( JsonNode model ) { Set<String> result = new HashSet<String>(); if (model.get("subTypes") != null) { JsonNode st = model.get("subTypes"); if (st instanceof ArrayNode) { ArrayNode sta = (ArrayNode) st; for (int i = 0; i < sta.size(); i++) { result.add(sta.get(i).asText()); } } } return result; } public String remapAbstractType( String jsonType ) { return innerRemapType(jsonType, false, ""); } public String remapConcreteType( String jsonType, String apiVersion ) { return innerRemapType(jsonType, true, apiVersion); } public String innerRemapType( String jsonType, boolean concrete, String apiVersion ) { String listAry = "List["; if ( jsonType.startsWith( listAry ) ) { return (concrete ? "List<" : "List<") + innerRemapType( jsonType.substring(listAry.length(), jsonType.length()-1 ), concrete, apiVersion ) + ">"; } else if ( JavaPkgInfo.TypeMap.containsKey( jsonType.toLowerCase() ) ) { return JavaPkgInfo.TypeMap.get( jsonType.toLowerCase() ); } else { return jsonType + ( concrete ? "_impl_" + apiVersion : "" ); } } // /** // * Writes implemenattion mappings. // * // * // * @param apiVersion // * @throws IOException // */ // // private void writeProperties( String apiVersion, Collection<Apis> apis, Collection<Model> models ) throws IOException { // String base = myAbsoluteProjectFolder // + "/classes" // + "/ch/loway/oss/ari4java/generated"; // String fName = base + "/" + apiVersion + ".properties"; // FileWriter outFile = new FileWriter(fName); // PrintWriter out = new PrintWriter(outFile); // out.println("# Implementation mapping for "+apiVersion); // for (Apis api : apis) { // String prop = "ch.loway.oss.ari4java.generated." // + api.getInterfaceName() // + " = " + api.getActionsPackage()+"."+api.getImplName(); // out.println(prop); // } // for (Model m : models) { // String prop = "ch.loway.oss.ari4java.generated." // + m.getInterfaceName() // + " = " + m.getModelPackage()+"."+m.getImplName(); // out.println(prop); // } // out.close(); // } private void writeAriBuilder( String apiVersion, AriBuilderInterface abi, Collection<Apis> apis, Collection<Model> models ) throws IOException { String thisClass = "AriBuilder_impl_" + apiVersion; List<String> ifToImplement = new ArrayList<String>( abi.knownInterfaces ); StringBuilder sb = new StringBuilder(); JavaGen.importClasses(sb, "ch.loway.oss.ari4java.generated." + apiVersion, Arrays.asList( new String[] { "ch.loway.oss.ari4java.generated." + apiVersion + ".models.*" , "ch.loway.oss.ari4java.generated." + apiVersion + ".actions.*", "ch.loway.oss.ari4java.generated.*", "ch.loway.oss.ari4java.ARI" })); sb.append("public class ").append( thisClass ).append( " implements AriBuilder {\n\n"); for (Apis api : apis) { String ifc = api.getInterfaceName(); ifToImplement.remove(ifc); sb.append( AriBuilderInterface.getMethod( ifc, apiVersion) ); } for (Model m : models) { String ifc = m.getInterfaceName(); ifToImplement.remove(ifc); sb.append( AriBuilderInterface.getMethod( ifc, apiVersion) ); } // do we have any unimplemented interface? for ( String ifc: ifToImplement ) { sb.append( AriBuilderInterface.getUnimplemented(ifc) ); } sb.append( "public ARI.ClassFactory getClassFactory() {\n" + " return new ClassTranslator_impl_" + apiVersion + "();\n" + "};\n\n" ); sb.append( "};"); saveToDisk( "classes/", "ch.loway.oss.ari4java.generated." + apiVersion, thisClass, sb.toString() ); } /** * Where the ari4java project resides. * * @param baseProjectFolder */ void setProjectFolder(String baseProjectFolder) { myAbsoluteProjectFolder =baseProjectFolder; } }